package com.mm.mindmap.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mm.common.domain.po.Result;
import com.mm.common.enums.ResultCode;
import com.mm.common.utils.DataUtil;
import com.mm.common.utils.theadPoolUtil.BulkExecuteHandler;
import com.mm.common.utils.theadPoolUtil.BulkExecuteUtil;
import com.mm.common.utils.theadPoolUtil.BulkTask;
import com.mm.common.utils.theadPoolUtil.BulkTaskCollection;
import com.mm.common.utils.theadPoolUtil.strategy.error.RetryAndCollectError;
import com.mm.mindmap.config.MindMapProperties;
import com.mm.mindmap.domain.dto.MindMapDTO;
import com.mm.mindmap.domain.es.MindMapDOC;
import com.mm.mindmap.domain.es.MindMapTemplate;
import com.mm.mindmap.domain.po.MindMap;
import com.mm.mindmap.mapper.MindMapMapper;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
@RequiredArgsConstructor
@Slf4j
public class EsServiceImpl extends ServiceImpl<MindMapMapper, MindMap>  {
    private final RestHighLevelClient client;
    private final MindMapProperties mmProperties;

    private static final int PAGE_SIZE = 2000;

    class Task extends BulkTask{

        List<MindMapDOC> mindMapDOCS;
        int page;
        int page_size;

        public Task(int page,int page_size) {
            this.page = page;
            this.page_size = page_size;
        }


        @Override
        public boolean executeFunction() throws IOException {
            //批量导入
            BulkRequest bulkRequest = new BulkRequest();

            Page<MindMap> pageSql = new Page<>(page, page_size);
            List<MindMap> mindMapList = lambdaQuery().page(pageSql).getRecords();
            List<MindMapDOC> mindMapDOCS = BeanUtil.copyToList(mindMapList, MindMapDOC.class);

            for(MindMapDOC doc : mindMapDOCS){
                doc.setScore(scoring1(doc));
                doc.setNodeList(doc.analysisNodeList());
                bulkRequest.add(new IndexRequest("mindmap")
                        .id(doc.getId().toString())
                        .source(JSONUtil.toJsonStr(doc), XContentType.JSON));
            }
            //发送请求，批量添加数据到es索引库中
            BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
            return true;
        }
    }

    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 初始化
     * @Describe: TODO
     **/
    @SneakyThrows
     public Result init() throws IOException {
        //创建索引
        CreateIndexRequest request = new CreateIndexRequest("mindmap");
        request.source(MindMapTemplate.MINDMAP_TEMPLATE, XContentType.JSON);
        client.indices().create(request, RequestOptions.DEFAULT);

        //批量获取导图和初始化ES
        long mindMapCount = Long.valueOf(lambdaQuery().count());
        int totalPage = (int) (mindMapCount % PAGE_SIZE == 0 ? mindMapCount / PAGE_SIZE : mindMapCount / PAGE_SIZE + 1);

        BulkTaskCollection taskList = BulkExecuteUtil.getTaskCollection(Task.class);
        BulkExecuteHandler bulkHandler = BulkExecuteUtil.getHandler(taskList);

        bulkHandler.setErrorStrategy(new RetryAndCollectError());

        //线程池批量导入
        for(int i =1; i<=totalPage; i++) {
            Task task = new Task(i,PAGE_SIZE);
            task.setName("导入页："+i);
            //新增文档
            taskList.add(task);
        }

        bulkHandler.executeTasks();
        log.info(bulkHandler.getExecuteInfo().toString());
        return Result.success();
    }




    /* ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 计算分数
     * @Describe: TODO
     **/
    private Double scoring1(MindMapDOC mindMap){
        return mindMap.getLike() * mmProperties.like_score
                + mindMap.getCollect() * mmProperties.collect_score
                + mindMap.getView() * mmProperties.view_score;
    }

    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 新增和修改
     * @Describe: TODO
     **/
    public Result updateES(MindMapDTO mindMapDTO) throws IOException {
        if(mindMapDTO == null
        || DataUtil.isNaturalNumInvalid(mindMapDTO.getId())
        || StrUtil.isBlank(mindMapDTO.getNodeList())
        || StrUtil.isBlank(mindMapDTO.getTitle())){
            return Result.failed(ResultCode.FAILED_PARAMS_VALIDATE);
        }
        MindMapDOC mindMap = BeanUtil.toBean(mindMapDTO, MindMapDOC.class);
        // 1.准备Request对象
        IndexRequest request = new IndexRequest("mindmap").id(mindMap.getId().toString());
        // 2.准备Json文档
        request.source(JSONUtil.toJsonStr(mindMap), XContentType.JSON);
        // 3.发送请求
        client.index(request, RequestOptions.DEFAULT);
        return Result.success();
    }
    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 删除
     * @Describe: TODO
     **/
    public Result deleteES(Long mid) throws IOException {
        if(DataUtil.isNaturalNumInvalid(mid)){
            return Result.failed(ResultCode.FAILED_PARAMS_VALIDATE);
        }
        // 1.准备Request，两个参数，第一个是索引库名，第二个是文档id
        DeleteRequest request = new DeleteRequest("mindmap", mid.toString());
        // 2.发送请求
        client.delete(request, RequestOptions.DEFAULT);
        return Result.success();
    }


    /** ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 搜索
     * @Describe: TODO
     **/
     public Result searchES(String search, Integer page) throws IOException {
        if(StrUtil.isBlank(search)
            || DataUtil.isNaturalNumInvalid(page)){
            return Result.failed(ResultCode.FAILED_PARAMS_VALIDATE);
        }

        SearchRequest searchRequest = new SearchRequest("mindmap");
        searchRequest.source().query(QueryBuilders.multiMatchQuery(search,"title","nodeList"));
//        searchRequest.source().sort("score", SortOrder.DESC);
        searchRequest.source().from((page-1)*20).size(20);

        HighlightBuilder highlightBuilder = new HighlightBuilder();


        HighlightBuilder.Field highlightTitle = new HighlightBuilder.Field("title");
        highlightBuilder.field(highlightTitle);
        HighlightBuilder.Field highlightFilecontent = new HighlightBuilder.Field("nodeList");
        highlightBuilder.field(highlightFilecontent);
        searchRequest.source().highlighter(
                highlightBuilder
                        .preTags("<span class=\"text-red\">")
                        .postTags("</span>")
        );
        SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);

        return Result.success(handleResponse(response));
    }

    /* ---------------------------------------------------------------------------------------------------------------------
     * @Method  : 解析来自es的响应
     * @Describe: TODO
     **/
     private Map<String,Object> handleResponse(SearchResponse response) {
        List<MindMapDOC> docList = new ArrayList<>();
        SearchHits searchHits = response.getHits();
        // 1.获取总条数
        long total = searchHits.getTotalHits().value;
        System.out.println("共搜索到" + total + "条数据");
        // 2.遍历结果数组
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            // 3.得到_source，也就是原始json文档
            String source = hit.getSourceAsString();
            // 4.反序列化
            MindMapDOC mindMap = JSONUtil.toBean(source, MindMapDOC.class);
            // 5.获取高亮结果
            Map<String, HighlightField> hfs = hit.getHighlightFields();
            if (CollUtil.isNotEmpty(hfs)) {
                // 5.1.有高亮结果，获取name的高亮结果
                HighlightField hf1 = hfs.get("title");
                if (hf1 != null) {
                    StringBuilder title = new StringBuilder();
                    // 5.2.获取第一个高亮结果片段，就是商品名称的高亮值
                    for(Text text : hf1.getFragments()){
                        title.append(text.string());
                    }
                    mindMap.setTitle(title.toString());
                }
                HighlightField hf2 = hfs.get("nodeList");
                if (hf2 != null) {
                    StringBuilder nodeList = new StringBuilder();
                    // 5.2.获取第一个高亮结果片段，就是商品名称的高亮值
                    for(Text text : hf2.getFragments()){
                        nodeList.append(text.string());
                    }
                    String nodeStr = nodeList.toString();
                    if(nodeStr.length()>200){
                        nodeStr = nodeStr.substring(0,200);
                    }
                    mindMap.setNodeList(nodeStr+"...");
                }
            }
            docList.add(mindMap);
        }
        Map ret = new HashMap();
        ret.put("mindMapList",docList);
        ret.put("pageCount",(total/20)+(total%20!=0?1:0));
        return ret;
    }
}
